#include "packets/packet_base.hpp"
#include "packets/em80_payload_factory.h"

#include <cassert>
#include <iterator>     //No need
#include <string>
#include <sstream>

#include <boost/range/begin.hpp>
#include <boost/range/distance.hpp>
#include <boost/range/end.hpp>



namespace em80_driver
{


EM80Frame::EM80Frame(int packet_size)
{

    //TODO::
    assert(packet_size >= 0 && packet_size <= 256);

    // single byte packet size
    frame_.reset(new Buffer(EM80_FRAME_HEADTAIL + packet_size));
    *(frame_->begin()) = 0xAA;
    *(frame_->begin() + 1) = 0x55;
    *(frame_->begin() + 2) = packet_size;

    packet_.first = frame_->begin() + 3;
    packet_.second = packet_.first + packet_size;

    payload_ = packet_.first;

}


//Combination with a namespace Method
EM80Frame::EM80Frame(const BufferRangeConst &frame, const BufferRangeConst &packet)
{
    assert(boost::distance(frame) >= EM80_MIN_FRAME_SIZE);
    assert(boost::distance(frame) <= EM80_MAX_FRAME_SIZE);
    assert(boost::distance(packet) <= EM80_MAX_PAYLOAD_SIZE);
    assert(std::distance(frame.first, packet.first) > 0 && std::distance(packet.second, frame.second) > 0);

    frame_.reset(new Buffer(boost::begin(frame), boost::end(frame)));  //we redirect the frame_ to point to the raw frame stream (with a range pair)

    packet_.first = frame_->begin() + std::distance(frame.first, packet.first);
    packet_.second = frame_->begin() + std::distance(frame.first, packet.second);

    payload_ = packet_.first;
}

EM80Packet::EM80Packet(int packet_size) : EM80Frame(packet_size)
{
    assert(boost::distance(packet_) > 0);
}

EM80Packet::EM80Packet(boost::shared_ptr<EM80Frame> raw) : EM80Frame(*raw)
{
    payload_ = packet_.first;
}

void EM80Packet::pushPayload(const EM80PayloadBasePtr &payload)
{
   payloadList_.push_back(payload);
}

void EM80Packet::popPayload(EM80PayloadBasePtr &payload)
{
    payload = payloadList_.front();
    payloadList_.pop_front();
}

bool EM80Packet::serialize()
{
    //TODO: Now, we confirm the fixed payload for commands (all existing command payload is counted)
    for(auto iter = payloadList_.begin(); iter != payloadList_.end(); iter++)
    {
        //TODO: check the pop process is successful or not
        bool serialized = (*iter)->serialize();
        if(!serialized) return false;
        payload_ = payload_ + static_cast<uint32_t>((*iter)->length) + 2;
    }

    payload_ = packet_.first;

    //TODO-urgent: CRC
    EM80Frame::CRC crc_calc;
    crc_calc.process_bytes(&(*(packet_.first - 1)), boost::distance(packet_) + static_cast<size_t>(1));       //change the pointer to void*
    *(packet_.second + 1) = static_cast<uint8_t>(crc_calc.checksum() >> 8);
    *(packet_.second) = static_cast<uint8_t>(crc_calc.checksum());

    return true;
}

bool EM80Packet::deserialize()
{
    //Factory to auto-make the individual payload
    //TODO: directly call the callback

    Buffer::iterator iter_begin(payload_);  //checking the sync

    while(payload_ != packet_.second)
    {
        std::string error;
        EM80PayloadBasePtr cur_payload = EM80PayloadFactory::createPayload(shared_from_this(), &error);  //only can be used internally here


        //TODO: check any errors with the error handler!!

        if(cur_payload)
        {
            //good payload, check if we skipped any data
            if(std::distance(iter_begin, payload_) > 0)
            {
                std::ostringstream ss;
                ss << "payload invalid with EM80, unknown data leading as valid frame. Discarding "
                   << std::distance(iter_begin, payload_) << "bytes.";
                //error_handler_(ss.str());
            }

            // call the specified payload deserializer
            bool payload_deserialized = cur_payload->deserialize();
            if(payload_deserialized)
            {
                // update the iter
                payload_ = payload_ + static_cast<uint32_t>(cur_payload->length) + 2;
                iter_begin = payload_;

                payloadList_.push_back(cur_payload);
            }
            else
            {
                payload_++;
            }

        }
        else   //assuming the packet is complete! (check by the "raw packet" logic CRC) --> it means the payload id is not register or the byte is corrupted!
        {
            //else, this was not a good payload, move on ot next byte after calling the error callback
            payload_++; //out of sync
        }

    }// while(payload_ != packet_.second)

    payload_ = packet_.first;   //It should be no NEEDED!!!
    return true;                //TODO: check with the size of the payloadList_ to make sure it is successful
}







EM80PacketPtr createFailed(int* p_num_bytes_needed, std::string *p_what, const std::string &what, int num_bytes_needed = 0)
{
    if(p_what != NULL) *p_what = what;
    if(p_num_bytes_needed != NULL) *p_num_bytes_needed = num_bytes_needed;

    return EM80PacketPtr();
}


EM80PacketPtr generateEM80Packet(const Buffer::const_iterator &begin,
                                 const Buffer::vector::const_iterator &end,
                                 int *p_num_bytes_needed, std::string *p_what)
{
    //initialize ouput variables
    if(p_num_bytes_needed != NULL) *p_num_bytes_needed = 0;
    if(p_what != NULL) p_what->clear();

    // need at least EM80_MIN_FRAME_SIZE bytes in buffer
    int buffer_size(std::distance(begin, end));
    if(buffer_size < EM80Frame::EM80_MIN_FRAME_SIZE)
        return createFailed(p_num_bytes_needed, p_what, "Buffer does not contain a complete frame",
                            EM80Frame::EM80_MIN_FRAME_SIZE - buffer_size);

    // buffer must begin with a start-of-frame
    if (EM80Frame::EM80_SOF_FRIST_VAL_FRAME != *begin ||
        EM80Frame::EM80_SOF_SECOND_VAL_FRAME != *(begin+1))
        return createFailed(p_num_bytes_needed, p_what, "Buffer must begin with start-of-frame character");

    // get a view of the packet
    BufferRangeConst view_packet;
    view_packet.first = begin + 3;
    view_packet.second = view_packet.first + *(begin + 2);

    // check length
    if(boost::distance(view_packet) > EM80Frame::EM80_MAX_PAYLOAD_SIZE)
        return createFailed(p_num_bytes_needed, p_what, "Invalid payload length");

    // get iterators to crc field and a view of the whole frame
    Buffer::const_iterator iter_crc(view_packet.second);
    Buffer::const_iterator iter_eof(iter_crc + 2);
    BufferRangeConst view_frame(begin, iter_eof);


    //************************************************************************
    // do we have enougth data in the buffer to complete the frame?
    //************************************************************************
    int frame_size = boost::distance(view_frame);
    if (buffer_size < frame_size)
        return createFailed(p_num_bytes_needed, p_what, "Buffer does not contain a complete frame",
                            frame_size - buffer_size);

    // is the crc valid? (crc16)
    unsigned short crc = (static_cast<unsigned short>(*iter_crc)) + (*(iter_crc + 1) << 8);
    EM80Frame::CRC crc_calc;

    //do we need to include the length?
    crc_calc.process_bytes(&(*(view_packet.first - 1)), boost::distance(view_packet) + static_cast<size_t>(1));       //change the pointer to void*


    if(crc != crc_calc.checksum())
    {
        return createFailed(p_num_bytes_needed, p_what, "Invalid checksum");
    }
    // frame looks good, construct the raw frame (using the frame cstr)
    boost::shared_ptr<EM80Frame> raw_frame(new EM80Frame(view_frame, view_packet));

    //return boost::shared_ptr<EM80Packet>();
    return boost::shared_ptr<EM80Packet>(new EM80Packet(raw_frame));        //We don't need to delete it manually!!

}

}
